import socket
import crcmod
import logging
import threading
from queue import Queue
import os
import requests
from pathlib import Path

_ENDIAN = 'big'

CRC_FUNC = crcmod.predefined.mkPredefinedCrcFun('modbus')

def rev_dict(d):
    return {v:k for k,v in d.items()}

class Message:
    HEAD = 0xFC03
    TAIL = 0xEB90
    TYPE_DICT = {
        'control': 0x11, # 控制消息，服务器下发给盒子
        'feedback': 0x12, # 反馈消息，盒子反馈给服务器
        'response': 0x13, # 响应消息，把0x11/0x12更改为0x13，回应给对方，无需解析字段
        'error': 0x14, # 故障消息，把0x11/0x12更改为0x14，回应给对方，固定字段e=XX
    }
    INSTR_DICT = {
        'handshake': 0x01, # 握手;	u=XX&c=XX
        'count': 0x02, # 上传统计人数;	c=XX&t=20211223140000&r=30
        'configure': 0x03, #配置; t=XX&s=0&c=600&a=0
        'toggle': 0x04, #开关人数统计	c=X&r=0
    }
    def __init__(self):
        self.head = self.HEAD #head
        self.addr = 0x0000 # A
        self.len = None # B, 2 bytes, C-F部分的字节数
        self.type = None # C, 1 byte
        self.instr = None # D, 1 byte, 指令
        self.data = None # E, N bytes
        self.crc = None # F, 2 bytes
        self.tail = self.TAIL

    def set(self, type, instr, data):
        self.head = self.head.to_bytes(2, _ENDIAN)
        self.addr = self.addr.to_bytes(2, _ENDIAN)
        self.tail = self.tail.to_bytes(2, _ENDIAN)

        self._set_type(type)
        self._set_instr(instr)
        self._set_data(data)
        self._set_len()
        self._set_crc()
        return self
    
    def parse(self, msg):
        self.head = int.from_bytes(msg[:2], _ENDIAN)
        self.addr = int.from_bytes(msg[2:4], _ENDIAN)
        self.len = int.from_bytes(msg[4:6], _ENDIAN)
        self.type = rev_dict(self.TYPE_DICT)[msg[6]]
        self.instr = rev_dict(self.INSTR_DICT)[msg[7]]
        self.data = msg[8:self.len+4].decode('ascii')
        self.crc = int.from_bytes(msg[self.len+4:self.len+6], _ENDIAN)
        self.tail = int.from_bytes(msg[self.len+6:self.len+8], _ENDIAN)
        return self
    
    """输入一个接收到的bytes，转换成response msg"""
    def response(self, msg):
        self.parse(msg)
        self.set('response', self.instr, self.data) # 重新计算 CRC
        return self
    
    def to_bytes(self):
        return self.head + self.addr + self.len + self.type + self.instr + self.data + self.crc + self.tail
    
    def _set_len(self):
        self.len = 4 + len(self.data)
        self.len = self.len.to_bytes(2, _ENDIAN)
    
    def _set_type(self, t):
        self.type = self.TYPE_DICT[t].to_bytes(1, _ENDIAN)
    
    def _set_instr(self, instr):

        self.instr = self.INSTR_DICT[instr].to_bytes(1, _ENDIAN)
    
    def _set_data(self, data):
        self.data = data.encode('ascii')
    
    def _set_crc(self):
        d = self.addr + self.len + self.type + self.instr + self.data
        self.crc = CRC_FUNC(d).to_bytes(2, _ENDIAN)

class Communicate:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.sock = socket.socket()
        self.msgs = Queue()
        self.receiver = threading.Thread(
            target=self.recv_fn,
            name="receiver"
        )
    
    def handshake(self, box_id, box_code):
        self.sock.connect((self.ip, self.port))
        data = 'u=%s&c=%s' % (box_id, box_code)
        msg = Message().set('feedback', 'handshake', data)
        ret = self.sock.sendall(msg.to_bytes())
        if ret is None:
            logging.info('socket, handshake, %s' % data)
        self.receiver.start()

    def send_count(self, cam_id, time, result):
        data = 'c=%s&t=%s&r=%d' % (cam_id, time, result)
        msg = Message().set('feedback', 'count', data)
        self.sock.sendall(msg.to_bytes())

    def recv_fn(self):
        while True:
            try:
                d0 = self.sock.recv(6)
            except Exception as e:
                logging.error('Communicate: sock.recv(6) error: %s' % (str(e)))
                break
            if int.from_bytes(d0[0:2], _ENDIAN) != Message.HEAD:
                logging.error('Communicate: receive wrong data head: %s' % (str(d0)))
                break

            length = int.from_bytes(d0[4:6], _ENDIAN)
            
            try:
                d1 = self.sock.recv(length+2)
            except Exception as e:
                logging.error('Communicate: sock.recv(6) error: %s' % (str(e)))
                break
            d = d0 + d1
            type = int.from_bytes(d[6:7], _ENDIAN)
            type = rev_dict(Message.TYPE_DICT)[type]
            instr = int.from_bytes(d[7:8], _ENDIAN)
            instr = rev_dict(Message.INSTR_DICT)[instr]
            data = d[8:-4].decode('ascii')
            self.msgs.put((type, instr, data))
            if type == 'control':
                Message().response(d)
        logging.error('something wrong in communicate recv')
        os._exit(-1)

class ImageSender:
    def __init__(self, server_url):
        self.server_url = server_url
        self.queue = Queue()
        self.thread = threading.Thread(
            target=self._work_fn,
            name="ImageSender"
        )
        self.thread.start()
    
    def send(self, box_id, box_code, image_file):
        self.queue.put((box_id, box_code, image_file), block=True)
    
    def _work_fn(self):
        while True:
            box_id, box_code, image_file = self.queue.get(block=True)
            self._send(box_id, box_code, image_file)
    
    def _send(self, box_id, box_code, image_file):
        headers ={
            'uid': box_id,
            'code': box_code,
        }
        name = Path(image_file).name
        files = {
            'file': (name, open(str(image_file), 'rb'), 'image/jpeg')
        }
        try:
            logging.info('send_image: %s, name: %s' % (str(headers), name))
            res = requests.post(self.server_url, headers=headers, files=files)
            logging.info('send_image response: %s' % (res.text))
        except Exception as e:
            logging.error('send_image error: %s' % str(e))
